/*-------------------------------------------
                Includes
-------------------------------------------*/
#include <dlfcn.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <opencv2/opencv.hpp>

#include "im2d.h"
#include "rga.h"
#include "RgaUtils.h"

#include "rkmedia/utils/mpp_decoder.h"
#include "rkmedia/utils/mpp_encoder.h"

#include "mk_mediakit.h"


typedef struct
{
    MppDecoder *decoder;  //创建一个 MppDecoder类的实例decoder，MppDecoder类定义了很多成员函数，由实例decoder继承
    MppEncoder *encoder;
    mk_media media;
    mk_pusher pusher;
    const char *push_url;

    mk_player player;
    // Yolov8Custom yolo;


    int video_type=264;

    int push_rtsp_port;
    std::string push_path_first;
    std::string push_path_second;


} rknn_app_context_t;

void release_media(mk_media *ptr)
{
    if (ptr && *ptr)
    {
        mk_media_release(*ptr);
        *ptr = NULL;
    }
}

void release_pusher(mk_pusher *ptr)
{
    if (ptr && *ptr)
    {
        mk_pusher_release(*ptr);
        *ptr = NULL;
    }
}

// 解码后的数据回调函数
//这部分逻辑是解码后再编码，摄像头的rtsp解码后编码在推流到新的rtsp，所以src既是解码后的输出缓冲区，也是编码的输入缓冲区，解码后再编码，实现共享缓冲区，零拷贝机制
void mpp_decoder_frame_callback(void *userdata, int width_stride, int height_stride, int width, int height, int format, int fd, void *data)
{
      //上面回调函数里参数是userdatas，这里创建了一个上下文 ctx，然后用 userdata 把它“递”给回调函数，即userdata实际里面是ctx，ctx指向结构体，里面有编解码器和推流信息
    rknn_app_context_t *ctx = (rknn_app_context_t *)userdata;   
    int ret = 0;
    // 帧画面计数
    static int frame_index = 0;
    frame_index++;

    void *mpp_frame = NULL;
    int mpp_frame_fd = 0;
    void *mpp_frame_addr = NULL;
    int enc_data_size;

    // rga原始数据aiqiyi
    rga_buffer_t origin;    
    rga_buffer_t src;

    // 编码器准备
    if (ctx->encoder == NULL)      //CTX里的编码器如果为NULL，则开辟一个
    {
        MppEncoder *mpp_encoder = new MppEncoder();   //new一个编码器，并返回指针
        MppEncoderParams enc_params;                 //定义编码数据
        memset(&enc_params, 0, sizeof(MppEncoderP arams));   //先初始化enc_params内存块（清零）
        enc_params.width = width;
        enc_params.height = height;
        enc_params.hor_stride = width_stride;
        enc_params.ver_stride = height_stride;               //给内存块赋值编码数据
        enc_params.fmt = MPP_FMT_YUV420SP;
        enc_params.type = MPP_VIDEO_CodingAVC;
        mpp_encoder->Init(enc_params, NULL);
        ctx->encoder = mpp_encoder;                  //赋值后编码器不再为NULL
    }

    // 编码准备
    int enc_buf_size = ctx->encoder->GetFrameSize(); //size=结构体成员encoder，类型是 MppEncoder*，即指向一个 MppEncoder类（所以encoder是MppEncode类的实例），调用MppEncoder类的成员GetFrameSize方法
    char *enc_data = (char *)malloc(enc_buf_size);  //声明一块内存用于存放编码后数据，大小用GetFrameSize获取

    // 获取解码后的帧
    mpp_frame = ctx->encoder->GetInputFrameBuffer();  
    // 获取解码后的帧fd
    mpp_frame_fd = ctx->encoder->GetInputFrameBufferFd(mpp_frame);  //fd=buffer这块内存的句柄,用这个来操作这块内存，解码后的输出缓冲区fd会被编码器输入缓冲区fd接管
    // 获取解码后的帧地址
    mpp_frame_addr = ctx->encoder->GetInputFrameBufferAddr(mpp_frame);

    // 复制到另一个缓冲区rgb_img，避免修改mpp解码器缓冲区
    // 使用的是硬件加速RK RGA 处理单元对解码后的 YUV 视频帧进行格式转换，从 YUV420SP 转换为 RGB888 格式
    origin = wrapbuffer_fd(fd, width, height, RK_FORMAT_YCbCr_420_SP, width_stride, height_stride);//wrapbuffer_fd将fd指向的内存区域包装成 rga_buffer_t 类型，只是将原始的 YUV 数据封装成一个可以操作的缓冲区对象。
    // origin和src这两行和颜色转换没有关系
    src = wrapbuffer_fd(mpp_frame_fd, width, height, RK_FORMAT_YCbCr_420_SP, width_stride, height_stride);
    // 创建一个等宽高的空对象
    cv::Mat origin_mat = cv::Mat::zeros(height, width, CV_8UC3);
    rga_buffer_t rgb_img = wrapbuffer_virtualaddr((void *)origin_mat.data, width, height, RK_FORMAT_RGB_888);//将刚刚创建的origin_mat包装成 rga_buffer_t 对象,此时，rgb_img 就是一个准备好接受 RGB 数据的缓冲区。
    imcopy(origin, rgb_img);//将原始的 YUV 数据（通过 origin）复制并转换到目标的 RGB 图像 rgb_img 中。即imcopy 才是负责将 YUV420SP 格式的数据转换为 RGB888 格式

    // 将当前时间点转换为毫秒级别的时间戳
    auto millis = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now()).time_since_epoch().count();
    if (ret != 0)
    {
        printf("inference model fail\n");
        goto RET;
    }
    imcopy(rgb_img, src);   //将解码数据转化rgb后复制到src，src 是用来存储处理后的图像数据的最终缓冲区，即rgb—_img只是一个媒介，为了避免直接修改mpp解码器缓冲区
    memset(enc_data, 0, enc_buf_size);     //清空enc data内存
    enc_data_size = ctx->encoder->Encode(mpp_frame, enc_data, enc_buf_size);   //调用编码，编码成264并放回编码后数据大小
    ret = mk_media_input_h264(ctx->media, enc_data, enc_data_size, millis, millis); //将编码后的 H264 数据输入到媒体模块中进行处理。时间戳用于提交处理时间
    if (ret != 1)
    {
        printf("mk_media_input_frame failed\n");
    }

RET: // tag
    if (enc_data != nullptr)
    {
        free(enc_data);      //释放内存
    }
}

void API_CALL on_track_frame_out(void *user_data, mk_frame frame)   //解码的回调函数
{
    rknn_app_context_t *ctx = (rknn_app_context_t *)user_data;
    const char *data = mk_frame_get_data(frame);    //从RTSP流获取压缩的h264数据
    // ctx->dts = mk_frame_get_dts(frame);
    // ctx->pts = mk_frame_get_pts(frame);
    size_t size = mk_frame_get_data_size(frame);
    ctx->decoder->Decode((uint8_t *)data, size, 0);  //解码h264数据
    // mk_media_input_frame(ctx->media, frame);
}

void API_CALL on_mk_push_event_func(void *user_data, int err_code, const char *err_msg)   //推流rtsp结束的回调函数
{
    rknn_app_context_t *ctx = (rknn_app_context_t *)user_data;
    if (err_code == 0)
    {
        // push success
        log_info("push %s success!", ctx->push_url);
        printf("push %s success!\n", ctx->push_url);
    }                                                                        //用于通知推流操作的结果
    else
    {
        log_warn("push %s failed:%d %s", ctx->push_url, err_code, err_msg);
        printf("push %s failed:%d %s\n", ctx->push_url, err_code, err_msg);
        release_pusher(&(ctx->pusher));
    }
}

void API_CALL on_mk_media_source_regist_func(void *user_data, mk_media_source sender, int regist)   //处理推流的注册和推流的注销的回调函数
{
    printf("mk_media_source:%x\n", sender);
    rknn_app_context_t *ctx = (rknn_app_context_t *)user_data;
    const char *schema = mk_media_source_get_schema(sender);  // 获取与媒体源相关的协议类型（rtsp:// 或 rtmp://）
    if (strncmp(schema, ctx->push_url, strlen(schema)) == 0)    //判断退流协议类型是否为我们指定的推流url的类型
    {
        // 判断是否为推流协议相关的流注册或注销事件
        printf("schema: %s\n", schema);
        release_pusher(&(ctx->pusher));        //注册
        if (regist)
        {
            ctx->pusher = mk_pusher_create_src(sender);   //创建推流器
            mk_pusher_set_on_result(ctx->pusher, on_mk_push_event_func, ctx);    //推流结果和推流结束的回调函数都用on_mk_push_event_func
            mk_pusher_set_on_shutdown(ctx->pusher, on_mk_push_event_func, ctx); 
            //            mk_pusher_publish(ctx->pusher, ctx->push_url);
            log_info("push started!");
            printf("push started!\n");
        }
        else
        {
            log_info("push stoped!");
            printf("push stoped!\n");       //注销
        }
        printf("push_url:%s\n", ctx->push_url);    
    }
    else
    {                                                              //rtsp流地址错误
        printf("unknown schema:%s\n", schema);   
    }
}

void API_CALL on_mk_play_event_func(void *user_data, int err_code, const char *err_msg, mk_track tracks[], int track_count)
//主要是在媒体播放事件中处理播放成功与失败的情况的回调函数
//接受到rtsp流的时候就开始进行，播放这个事件其中包括了 解码，以及推流的准备
{
    rknn_app_context_t *ctx = (rknn_app_context_t *)user_data;
    if (err_code == 0)
    {
        // success  ，播放成功即接受rtsp成功，并开始解码
        printf("play success!");
        int i;
        ctx->push_url = "rtmp://localhost/live/stream";  //设置推流地址
        ctx->media = mk_media_create("__defaultVhost__", ctx->push_path_first.c_str(), ctx->push_path_second.c_str(), 0, 0, 0); //设置推流对象
        for (i = 0; i < track_count; ++i)
        {
            if (mk_track_is_video(tracks[i]))   //遍历所有通道，如果是视频通道则初始化
            {
                log_info("got video track: %s", mk_track_codec_name(tracks[i]));
                // 监听track数据回调
                mk_media_init_track(ctx->media, tracks[i]);    
                mk_track_add_delegate(tracks[i], on_track_frame_out, user_data); //视频轨道的回调函数 on_track_frame_out，即解码的额回调函数，每次视频帧到达时，都会调用该回调函数处理。
            }
        }
        mk_media_init_complete(ctx->media);  //调用mk media的函数，1表示初始化成功
        mk_media_set_on_regist(ctx->media, on_mk_media_source_regist_func, ctx);   //前面定义的注册回调（推流）
    }
    else
    {
        //failed
        printf("play failed: %d %s", err_code, err_msg);   
    }
}

void API_CALL on_mk_shutdown_func(void *user_data, int err_code, const char *err_msg, mk_track tracks[], int track_count)
{
    printf("play interrupted: %d %s", err_code, err_msg);              //播放中断
}

int process_video_rtsp(rknn_app_context_t *ctx, const char *url)    //rtsp处理函数，具体为解码，解码后在调用解码后的回调函数（回调函数里在编码，然后推流）
{

    // MPP 解码器
    if (ctx->decoder == NULL)
    {
        MppDecoder *decoder = new MppDecoder();           // 创建解码器
        decoder->Init(ctx->video_type, 30, ctx);          // 初始化解码器
        decoder->SetCallback(mpp_decoder_frame_callback); // 设置回调函数，用来处理解码后的数据
        ctx->decoder = decoder;                        // 将解码器赋值给上下文
    }

    mk_player player = mk_player_create();  //创建播放对象
    ctx->player = player;
    mk_player_set_on_result(player, on_mk_play_event_func, ctx); //设置播放结果的回调函数（解码的具体实现）
    mk_player_set_on_shutdown(player, on_mk_shutdown_func, ctx); //播放中断的回调函数
    mk_player_play(player, url);  //启动播放器并传入播放的url ，即摄像头rtsp

    printf("enter any key to exit\n");
    getchar();

    if (player)
    {
        mk_player_release(player);  //在退出后，如果播放器对象 player 存在，调用 mk_player_release 释放播放器的资源
    }
    return 0;
}

int main(int argc, char **argv)
{
    int status = 0;
    int ret;

    // if (argc != 2)
    // {
    //     printf("Usage: %s<video_path>\n", argv[0]);
    //     return -1;
    // }
    // char *stream_url = argv[1];               // 视频流地址
    char *stream_url = "rtsp://admin:Tangerine!123@192.168.124.29:554/Streaming/Channels/101";
    int video_type = 264;           

    // 初始化流媒体
    mk_config config;
    memset(&config, 0, sizeof(mk_config));
    config.log_mask = LOG_CONSOLE;
    config.thread_num = 4;
    mk_env_init(&config);
    mk_rtsp_server_start(3554, 0);

    rknn_app_context_t app_ctx;                      // 创建上下文
    memset(&app_ctx, 0, sizeof(rknn_app_context_t)); // 初始化(清零）上下文
    app_ctx.video_type = video_type;
    app_ctx.push_path_first = "yunyan-live";
    app_ctx.push_path_second = "test";

    process_video_rtsp(&app_ctx, stream_url);  //处理rtsp流 （程序整个流程是播放-->解码-->编码-->推流，）

    printf("waiting finish\n");
    usleep(3 * 1000 * 1000);

    if (app_ctx.encoder != nullptr)
    {
        delete (app_ctx.encoder);
        app_ctx.encoder = nullptr;
    }

    return 0;
}